-- 看门狗服务
-- Author: Colocasia
-- Date: 2019-06-16

local skynet = require "skynet";
require "skynet.manager";
local socket = require 'us_socket';
local us_rpc = require 'us_rpc';
local timer = require 'timer';
local log = require "log";
local handler = require 'handler';
local account = require 'account';

local COMMON_CONST =  require 'common_const';

local SEND_SESSION_NO_RET = 0

local watchdog = {} -- watchdog 处理
local SOCKET = {}   -- socket处理
local gate;         -- gate服务


local stash = {};   -- stash暂存区
-- local agents = {};  -- agent列表
local tokens = {};  -- 可登录的token
local players = {}; -- player列表
local clients = {};  -- fd->guid


-- 错误处理
function on_cmd_err(msg)
    log.error(msg);
end

-- 发送回应
local function send_package(fd, pack)
    if not fd then return end
	socket.send(fd, pack)
end

-- 处理请求
local function request(fd, name, msg, response)
    log.info(string.format("CLIENT-MSG request = %s", name));
    local f = assert(watchdog[name], string.format("watchdog cmd=>%s not found", name));
    local addr = assert(stash[fd]);
    local r = f(fd, addr, msg) or {}
    -- log.dump(r, "response");
    if response then
        log.info(string.format("CLIENT-MSG response = %s", name));
        return response(r);
    else
        log.warning(string.format("CLIENT-MSG ERROR, NO RESPONSE = %s", name));
    end
end

---踢出客户端
local function kick_client(fd)
    if not gate then return end
    skynet.send(gate, 'lua', 'kick', fd);
end

-- 玩家离开
local function player_exit(guid)
    local p = players[guid];
    if not p then return end
    clients[p.client] = nil;
    p.client = nil;
    skynet.send(p.agent, 'lua', 'exit', guid);
end

-- 玩家退出客户端代理
local function exit_agent(client)
    local player_guid = clients[client];
    if player_guid then
        log.info('[watchdog]agent exit fd = %s, player = %s', client, player_guid);
        player_exit(player_guid);
    else
        log.info('[watchdog]agent already exit fd = %s, player = %s', client, player_guid);
    end
end

-- 客户端代理关闭
local function close_agent(guid)
    if not guid then return end
    log.debug('[watchdog]agent close player = %s', guid);

    local p = players[guid];
    if p then
        assert(p.agent)
        if p.fd then
            log.info('[watchdog] kick user fd = [%s], player = %s', p.client, guid);
            kick_client(p.client);
        end
        skynet.send(p.agent, "lua", "close");
        log.info('[watchdog] user close succeed. player = [%s], agent = %s', guid, skynet.address(p.agent));
    else
        log.info('[watchdog] user already closed player = %s', guid);
    end
end

-- 关闭所有代理
local function close_agents()

end

-- 关闭一个暂存区的客户端
local function close_stash(fd)
    stash[fd] = nil
end

---获得agent
local function get_agent(conf)
    local player_guid = conf.guid;
    local player_client = conf.client;

    local player = players[player_guid];
    local agent = player and player.agent;
    
    if not agent then
        agent = skynet.newservice("us_agent");
        skynet.call(agent, "lua", "start", conf);
    end

    if player_client then       -- player try to login
        if player and player.client then
            kick_client(player.client)
        end
    else
        skynet.send(agent, 'lua', 'heartbeat');
    end

    local player = {
        client = player_client,
        guid = player_guid,
        agent = agent,
    }
    players[player_guid] = player

    if player.client then
        player.client = player_client;
        clients[player_client] = player_guid;
        skynet.call(agent, 'lua', 'enter', conf)
    end 

    return agent
end

-- socket 一个客户端连接打开
function SOCKET.open(fd, addr)
    log.info("[watchdog]socket open new Client from " .. addr);
    -- TODO:打开一个连接进行token验证是否可以登录
    stash[fd] = addr;
    skynet.call(gate, 'lua', 'accept', fd);
end

-- socket 一个客户端连接关闭
function SOCKET.close(fd)
    log.info('[watchdog]socket close, fd = ' .. fd);
    exit_agent(fd);
    close_stash(fd);
    -- skynet.send(gate, 'lua', 'kick', fd);
end

-- socket 一个客户端连接错误
function SOCKET.error(fd, msg)
    log.info(string.format("[watchdog]socket error, fd = %d, msg = %s", fd, msg));
    exit_agent(fd);
    close_stash(fd);
    -- skynet.send(gate, 'lua', 'kick', fd);
end

-- socket 一个客户端连接警告
function SOCKET.warning(fd, size)
    log.warning(string.format("[watchdog]socket warning, fd = %d, size = %d", fd, size))
end

-- socket 一个客户端连接收到数据
function SOCKET.data(fd, msg, sz)
    log.info(string.format("[watchdog]socket data, fd = %d, msg = %s, size = %d", fd, msg, sz));
    local req_type, name, msg, response = us_rpc:unpack(msg, sz);

    -- skynet.trace()
    if 1 == req_type then       -- 请求
        local ok, result = xpcall(request, on_cmd_err, fd, name, msg, response);
        if ok and result then
            send_package(fd, result)
        end
    else                        -- 回应

    end
end

-- 启动
function watchdog.start_gate(conf)
    log.info('[watchdog]start gate serivice listen on port = %s, maxclient = %d', conf.port, conf.max_client);
    skynet.call(gate, "lua", "start", conf);
end

-- 关闭
function watchdog.close_gate()
    skynet.call(gate, "lua", "close")
    log.info('[watchdog]close gate finished..');
    close_agents()
end

-- 收到login转发的token
function watchdog.redirect(token)
    local info = account:parse_token(token);
    local guid = info[1];
    tokens[guid] = token;
end

-- 客户端登录
function watchdog.login(fd, addr, msg)
    log.debug('[watchdog]client login addr->%s token->%s', addr, msg.token);
    local info = account:parse_token(msg.token);
    local guid = info[1];
    if not tokens[guid] or msg.token ~= tokens[guid] then
        return handler:code("account_token_overdue");
    end

    local now = os.time()
    if now - info[2] > COMMON_CONST.DAY_SECS then
        tokens[guid] = nil;
        return handler:code("account_token_overdue");
    end

    -- 登录成功，分配agent
    log.debug("[watchdog]client login success");
    local conf = {
        watchdog = skynet.self(),
        gate = gate,
        client = fd, 
        addr = addr,
        guid = guid,
    }

    -- local agent = skynet.newservice("us_agent");
	-- skynet.call(agent, "lua", "start", conf);
    -- agents[fd] = agent;
    local agent = get_agent(conf);
    local msg = skynet.call(agent, "lua", "login");

    return handler:render({});
end

-- 踢出一个客户端
function watchdog.kick(fd)
    log.info('[watchdog]kick client, fd = %s', fd);
    kick_client(fd)
end

-- 关闭一个客户端代理
function watchdog.close_agent(guid)
    close_agent(guid);
end

---执行用户命令
function watchdog.exc_user_cmd(cmd, conf, ...)
    assert(conf.guid);
    local agent = get_agent(conf);
    log.debug("[watchdog]exc_user_cmd, cmd = %s, player = %s, agent = %s", cmd, conf.guid, skynet.address(agent));
    return skynet.call(agent, 'lua', 'exc_user_cmd', cmd, ...);
end

---发送用户命令
function watchdog.send_user_cmd(cmd, conf, ...)
    assert(conf.guid);
    local agent = get_agent(conf);
    log.debug("[watchdog]send_user_cmd, cmd = %s, player = %s, agent = %s", cmd, conf.guid, skynet.address(agent));
    skynet.send(agent, 'lua', 'send_user_cmd', cmd, ...);
end

-- 初始化
function watchdog.init()
    log.notice("=====>watchdog start<====");
    -- 注册watchdog服务名字
    local servicename = ".watchdog";
    skynet.register(servicename);

    skynet.dispatch("lua", function(session, source, cmd, subcmd, ...)
        local function ret(ok, ...)
            if session ~= SEND_SESSION_NO_RET then
                skynet.retpack(...)
            end
        end
        if "socket" == cmd then
            local f = assert(SOCKET[subcmd], string.format("watchdog socket cmd=>%s not found", subcmd));
            xpcall(f, on_cmd_err, ...)
            -- socket api don't need return
        else
            local f = assert(watchdog[cmd], string.format("watchdog cmd=>%s not found", cmd));
            ret(xpcall(f, on_cmd_err, subcmd, ...))
        end
    end)

    gate = skynet.newservice("us_gate");
end

skynet.start(watchdog.init);
